package com.yeziji.config.aspect;

import com.yeziji.annotation.RedisLock;
import com.yeziji.common.CommonResult;
import com.yeziji.common.CommonSymbol;
import com.yeziji.common.context.OnlineContext;
import com.yeziji.exception.ApiException;
import com.yeziji.service.cache.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.StringJoiner;

/**
 * 用户 redis lock 切面拦截
 *
 * @author hwy
 * @since 2023/12/06 10:57
 **/
@Slf4j
@Aspect
@Component
public class RedisLockAspect {
    @Resource
    private RedisService redisService;

    @Around("@annotation(redisLock)")
    public Object around(ProceedingJoinPoint pjp, RedisLock redisLock) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        String key = getKey(method, redisLock, pjp);

        int retryTimes = redisLock.action().equals(RedisLock.LockFailAction.CONTINUE) ? redisLock.retryTimes() : 0;
        boolean lock = redisService.lock(key, redisLock.keepMills(), retryTimes, redisLock.sleepMills());
        log.info("[{}: {}] 线程 {} 获取分布式锁 {}, 获取结果: {}", OnlineContext.getPlatform(), OnlineContext.getMdc(), Thread.currentThread().getId(), key, (lock ? " success" : " failed"));

        if (!lock) {
            Class<?> returnType = method.getReturnType();
            if (returnType == CommonResult.class) {
                return CommonResult.failed(redisLock.failMsg());
            } else {
                return null;
            }
        }
        //得到锁,执行方法，释放锁
        try {
            return pjp.proceed();
        } catch (ApiException e) {
            log.warn("执行分布式锁方法异常: {}", e.getMessage());
            throw e;
        } catch (Throwable e) {
            log.error("执行分布式锁方法异常: {}", e.getMessage(), e);
            throw e;
        } finally {
            boolean releaseResult = redisService.releaseLock(key);
            log.info("[{}: {}] 线程 {} 释放分布式锁 {}, 释放结果: {}", OnlineContext.getPlatform(), OnlineContext.getMdc(), Thread.currentThread().getId(), key, (releaseResult ? " success" : " failed"));
        }
    }

    /**
     * 根據方法来获取对应的 redis key
     *
     * @param method    方法
     * @param redisLock 注解入参
     * @param pjp       切面入参
     * @return {@link String} 组装的 key
     * @throws Exception 组装异常
     */
    private String getKey(Method method, RedisLock redisLock, ProceedingJoinPoint pjp) throws Exception {
        StringJoiner keyJoiner = new StringJoiner("_");
        if (redisLock.concatTokenMsg() && OnlineContext.isHasSession()) {
            keyJoiner.add(OnlineContext.getUserId() + CommonSymbol.BOTTOM_HORIZONTAL_BAR + OnlineContext.getUsername());
        }
        if (StringUtils.isNotBlank(redisLock.lockKey())) {
            if (redisLock.useKeyPrefix()) {
                keyJoiner.add(redisLock.lockKey()).add(method.getName());
            } else {
                keyJoiner.add(method.getName());
            }
        }

        if (redisLock.concatArgs()) {
            int indexOfArgs = redisLock.indexOfArgs();
            String fieldName = redisLock.fieldName();
            Object[] args = pjp.getArgs();
            // 下标越界校验
            if (indexOfArgs > -1 && indexOfArgs < args.length) {
                Object targetArgs = args[indexOfArgs];
                // 入参非空校验
                if (targetArgs != null && "" != targetArgs) {
                    if (redisLock.isObject()) {
                        // 入参是一个对象 从对象中取值作为key
                        if (StringUtils.isNotBlank(fieldName)) {
                            Class<?> targetArgsClass = targetArgs.getClass();
                            Field targetArgsClassDeclaredField = targetArgsClass.getDeclaredField(fieldName);
                            targetArgsClassDeclaredField.setAccessible(true);
                            Object targetFieldValue = targetArgsClassDeclaredField.get(targetArgs);
                            // 反射获取的属性值 非空校验
                            if (targetFieldValue == null || "".equals(targetFieldValue)) {
                                log.error("分布式锁拼接 key 不能为空 {}", args);
                            } else {
                                keyJoiner.add(targetFieldValue.toString());
                            }
                        }
                    } else {
                        // 入参直接取值作为key
                        keyJoiner.add(targetArgs.toString());
                    }
                } else {
                    log.error("分布式锁拼接 key 不能为空 {}", args);
                }
            }
        }
        return keyJoiner.toString();
    }
}
